package com.xy6.jvm.thread.threadlocal;

import java.util.Random;

/**
 * ThreadLocal用法示例
 * <pre>
 * 实现a、b两个线程age在不同时刻打印的值是完全相同的。
 * 这个程序通过妙用ThreadLocal，既实现多线程并发，游兼顾数据的安全性。
 * 
 * ThreadLocal是什么？它一个线程局部变量，不是一个Thread。
 * 线程局部变量作用：为每一个使用该变量的线程都提供一个变量值的副本，使每一个线程都可以独立地改变自己的副本，
 * 而不会和其它线程的副本冲突。从而实现并发访问。
 * 
 * 对于多线程资源共享的问题，同步机制采用了“以时间换空间”的方式，而ThreadLocal采用了“以空间换时间”的方式。
 * 前者仅提供一份变量，让不同的线程排队访问，而后者为每一个线程都提供了一份变量，因此可以同时访问而互不影响。
 * 
 * ThreadLocal使用场合：解决多线程中数据因并发产生不一致问题。
 * ThreadLocal为每个线程的中并发访问的数据提供一个副本，通过访问副本来运行业务，这样的结果是耗费了内存，
 * 单大大减少了线程同步所带来性能消耗，也减少了线程并发控制的复杂度。
 * 
 * ThreadLocal和Synchonized区别：
 * 1 ThreadLocal和Synchonized都用于解决多线程并发访问。但是ThreadLocal与synchronized有本质的区别：
 * synchronized是利用锁的机制，使变量或代码块在某一时该只能被一个线程访问。
 * 而ThreadLocal为每一个线程都提供了变量的副本，使得每个线程在某一时间访问到的并不是同一个对象，这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反，它用于在多个线程间通信时能够获得数据共享。
 * 2 Synchronized用于线程间的数据共享，而ThreadLocal则用于线程间的数据隔离。
 * 3 当然ThreadLocal并不能替代synchronized,它们处理不同的问题域。Synchronized用于实现同步机制，比ThreadLocal更加复杂。
 * 
 * http://blog.51cto.com/lavasoft/51926
 * </pre>
 * 
 * @author zhang
 *
 */
public class ThreadLocalDemo implements Runnable {

	// 创建线程局部变量studentLocal，用来保存Student对象
	private final static ThreadLocal<Student> studentLocal = new ThreadLocal<>();

	@Override
	public void run() {
		accessStudent();
	}

	/**
	 * 业务方法
	 */
	public void accessStudent() {
		String currentThreadName = Thread.currentThread().getName();
		System.out.println(currentThreadName + " is running!");
		Random random = new Random();
		int age = random.nextInt(100);
		System.out.println("thread " + currentThreadName + " set age to:" + age);
		// 获取一个Student对象，并将随机数年龄插入到对象属性中
		Student student = getStudent();
		student.setAge(age);
		System.out.println("thread " + currentThreadName + " first read age is:" + student.getAge());
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("thread " + currentThreadName + " second read age is:" + student.getAge());
	}

	protected Student getStudent() {
		// 获取本地线程变量并强制转换为Student类型
		Student student = (Student) studentLocal.get();
		// 线程首次执行此方法的时候，studentLocal.get()肯定为null
		if (student == null) {
			// 创建一个Student对象，并保存到本地线程变量studentLocal中
			student = new Student();
			studentLocal.set(student);
		}
		return student;
	}

	public static void main(String[] args) {
		ThreadLocalDemo td = new ThreadLocalDemo();
		Thread t1 = new Thread(td, "a");
		Thread t2 = new Thread(td, "b");
		t1.start();
		t2.start();
	}

}
